Skip to content

Golang版本的 Selpg

关于Selpg的说明

开发Linux 命令行实用程序中的selpg Selpg的C语言版本

具体代码

我的Github仓库

开发过程

程序总体设计

在看完上面开发Linux 命令行实用程序中的selpg的内容以及分析完Selpg的C语言版本后,我发现命令行程序的开发主要有以下几个部分:

  • 读取命令行输入的指令
  • 解析命令,将其中的参数结构化并检验是否合法
  • 实现命令对应的操作
  • 输入命令不合法或者操作出错时的提示

读取命令行输入的指令并解析,将其中的参数结构化

在go的开发中,我把读取指令及解析参数放到了一个部分,因为在golang中,提供了强大的flag(Pflag)库,我们很轻松就可以使用它完成指令的读取以及解析如下:

sa := new(selpg_args)
// Get args by flag(Pflag)
flag.IntVar(&sa.start_page, "s", -1, "The start page")
flag.IntVar(&sa.end_page, "e", -1, "The end page")
flag.IntVar(&sa.page_len, "l", -1, "The length of the page")
flag.StringVar(&sa.print_dest, "d", "", "The destination to print")

f_flag := flag.Bool("f", false, "")
flag.Parse()

if *f_flag {
    sa.page_type = false
    sa.page_len = -1
} else {
    sa.page_type = true // page_type default True
}

sa.in_filename = ""

if flag.NArg() == 1 {
    sa.in_filename = flag.Arg(0)
}

由于题目要求,我们使用的是Pflag, 在导入的时候,直接 import flag "github.com/spf13/pflag"即可,通过该库,这一步很快就得以完成指令的读取与解析

检验参数是否合法

这一步主要是检验参数是否过多, start_page和end_page是否输入且输入是否合法以及--l和--f是否冲突,因为我们的start_page和end_page初始为-1,检验是否小于等于0不仅能检验是否合法,还能检验是否输入了start_page和end_page

func validate_args(sa selpg_args, rest int) { // 检验输入参数是否合法,rest为剩余的参数数目
    if rest > 1 {
        fmt.Fprintf(os.Stderr, "./selpg: too much arguments\n")
        usage()
        os.Exit(1)
    }
    if sa.start_page <= 0 || sa.end_page <= 0 || sa.end_page < sa.start_page {
        fmt.Fprintf(os.Stderr, "./selpg: Invalid start, end page or line number")
        usage()
        os.Exit(1)
    }
    if sa.page_type == false && sa.page_len != -1 {
        fmt.Fprintf(os.Stderr, "./selpg: Conflict flags: -f and -l")
        usage()
        os.Exit(1)
    }
}

实现命令对应的操作

按照c语言版本的程序,我们对应加上需要使用的参数

// initial
fin := os.Stdin
fout := os.Stdout
line_ctr := 0 /* line counter */
page_ctr := 1 /* page counter */
var inpipe io.WriteCloser
var err error // store err msg

判断有无输入文件,即判断是键盘输入还是文件输入

if sa.in_filename != "" {
    fin, err = os.Open(sa.in_filename)
    if err != nil {
        fmt.Fprintf(os.Stderr, "./selpg: could not open input file \"%s\"\n", sa.in_filename)
        usage()
        os.Exit(1)
    }
    defer fin.Close()        // 函数返回前执行fin.Close()
}

判断输出方式

if sa.print_dest != "" {
    cmd := exec.Command("lp", "-d", sa.print_dest)
    inpipe, err = cmd.StdinPipe()
    if err != nil {
        fmt.Fprintf(os.Stderr, "could not open pipe to \"%s\"\n", sa.print_dest)
        usage()
        os.Exit(1)
    }
    defer inpipe.Close()
    cmd.Stdout= fout
    cmd.Start()
}

根据分页方式进行读入

if sa.page_type == true {
    line := bufio.NewScanner(fin)
    for line.Scan() {
        if page_ctr >= sa.start_page && page_ctr <= sa.end_page  {
            fout.Write([]byte(line.Text() + "\n"))
            if sa.print_dest != "" {
                inpipe.Write([]byte(line.Text() + "\n"))
            }
        }
        line_ctr++
        if line_ctr == sa.page_len {
            page_ctr++
            line_ctr = 0
        }
    }
} else {
    reader := bufio.NewReader(fin)
    for {
        pageContent, err := reader.ReadString('\f')
        if err != nil || err == io.EOF {
            if err == io.EOF {
                if page_ctr >= sa.start_page && page_ctr <= sa.end_page {
                    fmt.Fprintf(fout, "%s", pageContent)
                }
            }
        }
        break
        pageContent = strings.Replace(pageContent, "\f", "", -1)
        page_ctr++
        if page_ctr >= sa.start_page && page_ctr <= sa.end_page {
            fmt.Fprintf(fout, "%s", pageContent)
        }
    }
}

判断页数是否有误

if page_ctr < sa.start_page {
    fmt.Fprintf(os.Stderr, "./selpg:  start_page (%d) greater than total pages (%d), less output than expected\n", sa.start_page, page_ctr)
} else if page_ctr < sa.end_page {
    fmt.Fprintf(os.Stderr, "./selpg:  end_page (%d) greater than total pages (%d), less output than expected\n", sa.end_page, page_ctr)
}

输入命令不合法或者操作出错时的提示

对应原c语言程序中的Usage函数

func usage() {
    fmt.Fprintf(os.Stderr, "\nUSAGE: ./selpg --s start_page --e end_page [ --f | --l lines_per_page ] [ --d dest ] [ in_filename ]\n")
}

测试程序

bash生成测试数据

使用bash自动生成我们需要的测试程序

for ((i=1;i<200;i++))
do
echo "line" $i >> test
done
for ((i=1;i<20;i += 4))
do
echo "line" $i >> testpage
echo "line" $i+1 >> testpage
echo "line" $i+2 >> testpage
echo "line" $i+3 >> testpage
echo -e '\f' >> testpage
done

测试

$./selpg --s 1 --e 1 test

结果输出line1-199,正确

$./selpg --s 1 --e 3 testpage

$./selpg --s 1 --e 2 --f testpage

./selpg --s 1 --e 2 testpage 2>error_file

./selpg --s 1 --e 2 testpage >output_file 2>error_file